﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Net;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
using System.Windows.Threading;
using SABnzbdNET.Model;

namespace SABnzbdNET.ServiceAgents
{
    public class ServerAgent
    {
        private BackgroundWorker backgroundWorker = null; 
        private Action<ServerInfo, string> getInfoCallback;
        private Action runCommandCallback;
        private Action uploadCallback;

        public ServerAgent()
        {
            backgroundWorker = new BackgroundWorker();
            backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker_Completed);
            backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
            backgroundWorker.WorkerSupportsCancellation = true;
        }

        public void GetInfo(Action<ServerInfo, string> callback)
        {
            string temp = Properties.Settings.Default.ServerPath + Properties.Settings.Default.ServerPathMode_Status + Properties.Settings.Default.ServerPathParameters;
            string serverUrl = string.Format(temp, Properties.Settings.Default.ServerUsername, Properties.Settings.Default.ServerPassword, Properties.Settings.Default.ServerAPIKey);


            getInfoCallback = callback;
            var client = new WebClient();
            client.OpenReadCompleted += GetInfoCompleted;
            client.OpenReadAsync(new Uri(serverUrl, UriKind.Absolute));
        }

        private void GetInfoCompleted(object sender, OpenReadCompletedEventArgs e)
        {
            var serverInfo = new ServerInfo();
            try
            {
                // Create a new StreamReader instance using the specified Stream object.
                using (var streamreader = new StreamReader(e.Result))
                {
                    string xml = streamreader.ReadToEnd();
                    if (!String.IsNullOrEmpty(xml))
                        serverInfo = DeserializeObject<ServerInfo>(xml);
                    else
                        getInfoCallback(null, "No response from server");
                }
            }
            catch (Exception ex)
            {
                getInfoCallback(null, ex.Message);
            }

            getInfoCallback(serverInfo, string.Empty);
        }

        public void StartServer(Action callback)
        {
            string temp = Properties.Settings.Default.ServerPath + Properties.Settings.Default.ServerPathMode_Start + Properties.Settings.Default.ServerPathParameters;
            string serverUrlStart = string.Format(temp, Properties.Settings.Default.ServerUsername, Properties.Settings.Default.ServerPassword, Properties.Settings.Default.ServerAPIKey);

            RunCommand(serverUrlStart, callback);
        }

        public void PauseServer(Action callback)
        {
            string temp = Properties.Settings.Default.ServerPath + Properties.Settings.Default.ServerPathMode_Stop + Properties.Settings.Default.ServerPathParameters;
            string serverUrlStop = string.Format(temp, Properties.Settings.Default.ServerUsername, Properties.Settings.Default.ServerPassword, Properties.Settings.Default.ServerAPIKey);

            RunCommand(serverUrlStop, callback);
        }

        public void ShutDownServer(Action callback)
        {
            string temp = Properties.Settings.Default.ServerPath + Properties.Settings.Default.ServerPathMode_Shutdown + Properties.Settings.Default.ServerPathParameters;
            string serverUrlShutdown = string.Format(temp, Properties.Settings.Default.ServerUsername, Properties.Settings.Default.ServerPassword, Properties.Settings.Default.ServerAPIKey);

            RunCommand(serverUrlShutdown, callback);
        }

        public void UploadToServer(string fileName, string safeFileName, string newzBinId, string category
            , string script, string priority, Action callback)
        {
            string temp = Properties.Settings.Default.ServerPath + Properties.Settings.Default.ServerPathMode_AddFile + Properties.Settings.Default.ServerPathParameters;
            string serverUrlAddFile = string.Format(temp, Properties.Settings.Default.ServerUsername, Properties.Settings.Default.ServerPassword
                    , Properties.Settings.Default.ServerAPIKey, safeFileName, category, script, priority);

            temp = Properties.Settings.Default.ServerPath + Properties.Settings.Default.ServerPathMode_AddId + Properties.Settings.Default.ServerPathParameters;
            string serverUrlAddId = string.Format(temp, Properties.Settings.Default.ServerUsername, Properties.Settings.Default.ServerPassword
                , Properties.Settings.Default.ServerAPIKey, newzBinId, category, script, priority);

            uploadCallback = callback;
            backgroundWorker.RunWorkerAsync(new List<string> 
            { 
                string.Format(serverUrlAddFile), 
                fileName, 
                string.Format(serverUrlAddId), 
                newzBinId 
            });
        }

        public void CancelUploadToServer()
        {
            if (backgroundWorker.IsBusy)
                backgroundWorker.CancelAsync();
        }

        private void RunCommand(string command, Action callback)
        {
            runCommandCallback = callback;
            var client = new WebClient();
            client.OpenReadCompleted += RunCommandCompleted;
            client.OpenReadAsync(new Uri(command, UriKind.Absolute));
        }

        private void RunCommandCompleted(object sender, OpenReadCompletedEventArgs e)
        {
            runCommandCallback();
        }

        private static T DeserializeObject<T>(string xml)
        {
            XmlSerializer xs = new XmlSerializer(typeof(T));
            MemoryStream memoryStream = new MemoryStream(StringToUTF8ByteArray(xml));
            XmlTextWriter xmlTextWriter = new XmlTextWriter(memoryStream, Encoding.UTF8);
            return (T)xs.Deserialize(memoryStream);
        }

        private static Byte[] StringToUTF8ByteArray(string pXmlString)
        {
            UTF8Encoding encoding = new UTF8Encoding();
            byte[] byteArray = encoding.GetBytes(pXmlString);
            return byteArray;
        }

        private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            //upload file
            List<string> data = e.Argument as List<string>;
            if (!string.IsNullOrEmpty(data[1]))
                UploadFile(data[0], data[1]);
            if (!string.IsNullOrEmpty(data[3]))
                UploadId(data[2]);
        }

        private void backgroundWorker_Completed(object sender, RunWorkerCompletedEventArgs e)
        {
            uploadCallback();
        }

        private void UploadFile(string url, string file)
        {
            string bndy = "--" + Guid.NewGuid();

            var wr = (HttpWebRequest)WebRequest.Create(url);
            wr.Method = "POST";
            wr.ContentType = "multipart/form-data; boundary=" + bndy.Substring(2);
            using (var strm = wr.GetRequestStream())
            {
                using (var sr = new StreamWriter(strm, new System.Text.UTF8Encoding(false)))
                {
                    // Write "FILE".
                    sr.WriteLine(bndy);
                    sr.WriteLine("Content-Disposition: form-data; name=\"name\"; filename=\"" + Path.GetFileName(file) + "\"");
                    sr.WriteLine("Content-Type: application/x-nzb");
                    sr.WriteLine();
                    sr.Flush();
                    CopyFileToStream(file, strm);
                    strm.Flush();
                    sr.WriteLine();
                    // Write last boundary.
                    sr.Write(bndy);
                    sr.WriteLine("--");
                }
            }
            wr.GetResponse();
        }

        private void UploadId(string url)
        {
            var client = new WebClient();
            client.OpenRead(url);
        }

        private static void CopyFileToStream(string fileName, Stream strm)
        {
            using (var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                var buf = new byte[8192];
                while (true)
                {
                    int bytesRead = fs.Read(buf, 0, buf.Length);
                    if (bytesRead == 0) // EOF
                    {
                        break;
                    }
                    strm.Write(buf, 0, bytesRead);
                }
            }
        }
    }
}
